/** @file
  Implement the driver binding protocol for Asix AX88772 Ethernet driver.

  Copyright (c) 2011-2013, Intel Corporation. All rights reserved.<BR>
  SPDX-License-Identifier: BSD-2-Clause-Patent

**/

#include "Ax88772.h"

/**
  Verify the controller type

  @param [in] pThis                Protocol instance pointer.
  @param [in] Controller           Handle of device to test.
  @param [in] pRemainingDevicePath Not used.

  @retval EFI_SUCCESS          This driver supports this device.
  @retval other                This driver does not support this device.

**/
EFI_STATUS
EFIAPI
DriverSupported (
  IN EFI_DRIVER_BINDING_PROTOCOL * pThis,
  IN EFI_HANDLE Controller,
  IN EFI_DEVICE_PATH_PROTOCOL * pRemainingDevicePath
  )
{
  EFI_USB_DEVICE_DESCRIPTOR Device;
  EFI_USB_IO_PROTOCOL * pUsbIo;
  EFI_STATUS Status;

  //
  //  Connect to the USB stack
  //
  Status = gBS->OpenProtocol (
                  Controller,
                  &gEfiUsbIoProtocolGuid,
                  (VOID **) &pUsbIo,
                  pThis->DriverBindingHandle,
                  Controller,
                  EFI_OPEN_PROTOCOL_BY_DRIVER
                  );
  if (!EFI_ERROR ( Status )) {

    //
    //  Get the interface descriptor to check the USB class and find a transport
    //  protocol handler.
    //
    Status = pUsbIo->UsbGetDeviceDescriptor ( pUsbIo, &Device );
    if (!EFI_ERROR ( Status )) {

      //
      //  Validate the adapter
      //
      if (( VENDOR_ID != Device.IdVendor )
        || ( PRODUCT_ID != Device.IdProduct )) {
        Status = EFI_UNSUPPORTED;
      }
    }

    //
    //  Done with the USB stack
    //
    gBS->CloseProtocol (
           Controller,
           &gEfiUsbIoProtocolGuid,
           pThis->DriverBindingHandle,
           Controller
           );
  }

  //
  //  Return the device supported status
  //
  return Status;
}


/**
  Start this driver on Controller by opening UsbIo and DevicePath protocols.
  Initialize PXE structures, create a copy of the Controller Device Path with the
  NIC's MAC address appended to it, install the NetworkInterfaceIdentifier protocol
  on the newly created Device Path.

  @param [in] pThis                Protocol instance pointer.
  @param [in] Controller           Handle of device to work with.
  @param [in] pRemainingDevicePath Not used, always produce all possible children.

  @retval EFI_SUCCESS          This driver is added to Controller.
  @retval other                This driver does not support this device.

**/
EFI_STATUS
EFIAPI
DriverStart (
  IN EFI_DRIVER_BINDING_PROTOCOL * pThis,
  IN EFI_HANDLE Controller,
  IN EFI_DEVICE_PATH_PROTOCOL * pRemainingDevicePath
  )
{
  EFI_STATUS Status;
  NIC_DEVICE * pNicDevice;
  UINTN LengthInBytes;

  DBG_ENTER ( );

  //
  //  Allocate the device structure
  //
  LengthInBytes = sizeof ( *pNicDevice );
  Status = gBS->AllocatePool (
                  EfiRuntimeServicesData,
                  LengthInBytes,
                  (VOID **) &pNicDevice
                  );
  if ( !EFI_ERROR ( Status )) {
    DEBUG (( DEBUG_POOL | DEBUG_INIT,
              "0x%08x: Allocate pNicDevice, %d bytes\r\n",
              pNicDevice,
              sizeof ( *pNicDevice )));

    //
    //  Set the structure signature
    //
    ZeroMem ( pNicDevice, LengthInBytes );
    pNicDevice->Signature = DEV_SIGNATURE;

    //
    //  Connect to the USB I/O protocol
    //
    Status = gBS->OpenProtocol (
                    Controller,
                    &gEfiUsbIoProtocolGuid,
                    (VOID **) &pNicDevice->pUsbIo,
                    pThis->DriverBindingHandle,
                    Controller,
                    EFI_OPEN_PROTOCOL_BY_DRIVER
                    );

    if ( !EFI_ERROR ( Status )) {
      //
      //  Allocate the necessary events
      //
      Status = gBS->CreateEvent ( EVT_TIMER,
                                  TPL_AX88772,
                                  (EFI_EVENT_NOTIFY)Ax88772Timer,
                                  pNicDevice,
                                  (VOID **)&pNicDevice->Timer );
      if ( !EFI_ERROR ( Status )) {
        DEBUG (( DEBUG_POOL | DEBUG_INIT | DEBUG_INFO,
                  "0x%08x: Allocated timer\r\n",
                  pNicDevice->Timer ));

        //
        //  Initialize the simple network protocol
        //
        pNicDevice->Controller = Controller;
        SN_Setup ( pNicDevice );

        //
        //  Start the timer
        //
        Status = gBS->SetTimer ( pNicDevice->Timer,
                                 TimerPeriodic,
                                 TIMER_MSEC );
        if ( !EFI_ERROR ( Status )) {
          //
          //  Install both the simple network and device path protocols.
          //
          Status = gBS->InstallMultipleProtocolInterfaces (
                          &Controller,
                          &gEfiCallerIdGuid,
                          pNicDevice,
                          &gEfiSimpleNetworkProtocolGuid,
                          &pNicDevice->SimpleNetwork,
                          NULL
                          );

          if ( !EFI_ERROR ( Status )) {
            DEBUG (( DEBUG_POOL | DEBUG_INIT | DEBUG_INFO,
                      "Installed: gEfiCallerIdGuid on   0x%08x\r\n",
                      Controller ));
            DEBUG (( DEBUG_POOL | DEBUG_INIT | DEBUG_INFO,
                      "Installed: gEfiSimpleNetworkProtocolGuid on   0x%08x\r\n",
                      Controller ));
            DBG_EXIT_STATUS ( Status );
            return Status;
          }
          DEBUG (( DEBUG_ERROR | DEBUG_INIT | DEBUG_INFO,
                    "ERROR - Failed to install gEfiSimpleNetworkProtocol on 0x%08x\r\n",
                    Controller ));
        }
        else {
          DEBUG (( DEBUG_ERROR | DEBUG_INIT | DEBUG_INFO,
                    "ERROR - Failed to start the timer, Status: %r\r\n",
                    Status ));
        }
      }
      else {
        DEBUG (( DEBUG_ERROR | DEBUG_INIT | DEBUG_INFO,
                  "ERROR - Failed to create timer event, Status: %r\r\n",
                  Status ));
      }

      //
      //  Done with the USB stack
      //
      gBS->CloseProtocol (
             Controller,
             &gEfiUsbIoProtocolGuid,
             pThis->DriverBindingHandle,
             Controller
             );
    }

    //
    //  Done with the device
    //
    gBS->FreePool ( pNicDevice );
  }

  //
  //  Display the driver start status
  //
  DBG_EXIT_STATUS ( Status );
  return Status;
}


/**
  Stop this driver on Controller by removing NetworkInterfaceIdentifier protocol and
  closing the DevicePath and PciIo protocols on Controller.

  @param [in] pThis                Protocol instance pointer.
  @param [in] Controller           Handle of device to stop driver on.
  @param [in] NumberOfChildren     How many children need to be stopped.
  @param [in] pChildHandleBuffer   Not used.

  @retval EFI_SUCCESS          This driver is removed Controller.
  @retval EFI_DEVICE_ERROR     The device could not be stopped due to a device error.
  @retval other                This driver was not removed from this device.

**/
EFI_STATUS
EFIAPI
DriverStop (
  IN  EFI_DRIVER_BINDING_PROTOCOL * pThis,
  IN  EFI_HANDLE Controller,
  IN  UINTN NumberOfChildren,
  IN  EFI_HANDLE * pChildHandleBuffer
  )
{
  NIC_DEVICE * pNicDevice;
  EFI_STATUS Status;

  DBG_ENTER ( );

  //
  //  Determine if this driver is already attached
  //
  Status = gBS->OpenProtocol (
                  Controller,
                  &gEfiCallerIdGuid,
                  (VOID **) &pNicDevice,
                  pThis->DriverBindingHandle,
                  Controller,
                  EFI_OPEN_PROTOCOL_GET_PROTOCOL
                  );
  if ( !EFI_ERROR ( Status )) {
    //
    //  AX88772 driver is no longer running on this device
    //
    gBS->UninstallMultipleProtocolInterfaces (
              Controller,
              &gEfiSimpleNetworkProtocolGuid,
              &pNicDevice->SimpleNetwork,
              &gEfiCallerIdGuid,
              pNicDevice,
              NULL );
    DEBUG (( DEBUG_POOL | DEBUG_INIT,
                "Removed:   gEfiSimpleNetworkProtocolGuid from 0x%08x\r\n",
                Controller ));
    DEBUG (( DEBUG_POOL | DEBUG_INIT,
                "Removed:   gEfiCallerIdGuid from 0x%08x\r\n",
                Controller ));

    //
    //  Stop the timer
    //
    if ( NULL != pNicDevice->Timer ) {
      gBS->SetTimer ( pNicDevice->Timer, TimerCancel, 0 );
      gBS->CloseEvent ( pNicDevice->Timer );
      DEBUG (( DEBUG_POOL | DEBUG_INIT | DEBUG_INFO,
                "0x%08x: Released timer\r\n",
                pNicDevice->Timer ));
    }

    //
    //  Done with the device context
    //
    DEBUG (( DEBUG_POOL | DEBUG_INIT,
              "0x%08x: Free pNicDevice, %d bytes\r\n",
              pNicDevice,
              sizeof ( *pNicDevice )));
    gBS->FreePool ( pNicDevice );
  }

  //
  //  Return the shutdown status
  //
  DBG_EXIT_STATUS ( Status );
  return Status;
}


/**
  Driver binding protocol declaration
**/
EFI_DRIVER_BINDING_PROTOCOL  gDriverBinding = {
  DriverSupported,
  DriverStart,
  DriverStop,
  0xa,
  NULL,
  NULL
};


/**
  Ax88772 driver unload routine.

  @param [in] ImageHandle       Handle for the image.

  @retval EFI_SUCCESS           Image may be unloaded

**/
EFI_STATUS
EFIAPI
DriverUnload (
  IN EFI_HANDLE ImageHandle
  )
{
  UINTN BufferSize;
  UINTN Index;
  UINTN Max;
  EFI_HANDLE * pHandle;
  EFI_STATUS Status;

  //
  //  Determine which devices are using this driver
  //
  BufferSize = 0;
  pHandle = NULL;
  Status = gBS->LocateHandle (
                  ByProtocol,
                  &gEfiCallerIdGuid,
                  NULL,
                  &BufferSize,
                  NULL );
  if ( EFI_BUFFER_TOO_SMALL == Status ) {
    for ( ; ; ) {
      //
      //  One or more block IO devices are present
      //
      Status = gBS->AllocatePool (
                      EfiRuntimeServicesData,
                      BufferSize,
                      (VOID **) &pHandle
                      );
      if ( EFI_ERROR ( Status )) {
        DEBUG (( DEBUG_ERROR | DEBUG_POOL | DEBUG_INIT | DEBUG_INFO,
                  "Insufficient memory, failed handle buffer allocation\r\n" ));
        break;
      }

      //
      //  Locate the block IO devices
      //
      Status = gBS->LocateHandle (
                      ByProtocol,
                      &gEfiCallerIdGuid,
                      NULL,
                      &BufferSize,
                      pHandle );
      if ( EFI_ERROR ( Status )) {
        //
        //  Error getting handles
        //
        DEBUG (( DEBUG_ERROR | DEBUG_INIT | DEBUG_INFO,
                "Failure getting Telnet handles\r\n" ));
        break;
      }
      
      //
      //  Remove any use of the driver
      //
      Max = BufferSize / sizeof ( pHandle[ 0 ]);
      for ( Index = 0; Max > Index; Index++ ) {
        Status = DriverStop ( &gDriverBinding,
                              pHandle[ Index ],
                              0,
                              NULL );
        if ( EFI_ERROR ( Status )) {
          DEBUG (( DEBUG_WARN | DEBUG_INIT | DEBUG_INFO,
                    "WARNING - Failed to shutdown the driver on handle %08x\r\n", pHandle[ Index ]));
          break;
        }
      }
      break;
    }
  }
  else {
    if ( EFI_NOT_FOUND == Status ) {
      //
      //  No devices were found
      //
      Status = EFI_SUCCESS;
    }
  }

  //
  //  Free the handle array
  //
  if ( NULL != pHandle ) {
    gBS->FreePool ( pHandle );
  }

  //
  //  Remove the protocols installed by the EntryPoint routine.
  //
  if ( !EFI_ERROR ( Status )) {
    gBS->UninstallMultipleProtocolInterfaces (
            ImageHandle,
            &gEfiDriverBindingProtocolGuid,
            &gDriverBinding,
            &gEfiComponentNameProtocolGuid,
            &gComponentName,
            &gEfiComponentName2ProtocolGuid,
            &gComponentName2,
            NULL
            );
    DEBUG (( DEBUG_POOL | DEBUG_INIT | DEBUG_INFO,
            "Removed:   gEfiComponentName2ProtocolGuid from 0x%08x\r\n",
            ImageHandle ));
    DEBUG (( DEBUG_POOL | DEBUG_INIT | DEBUG_INFO,
              "Removed:   gEfiComponentNameProtocolGuid from 0x%08x\r\n",
              ImageHandle ));
    DEBUG (( DEBUG_POOL | DEBUG_INIT | DEBUG_INFO,
              "Removed:   gEfiDriverBindingProtocolGuid from 0x%08x\r\n",
              ImageHandle ));
  }

  //
  //  Return the unload status
  //
  return Status;
}


/**
Ax88772 driver entry point.

@param [in] ImageHandle       Handle for the image.
@param [in] pSystemTable      Address of the system table.

@retval EFI_SUCCESS           Image successfully loaded.

**/
EFI_STATUS
EFIAPI
EntryPoint (
  IN EFI_HANDLE ImageHandle,
  IN EFI_SYSTEM_TABLE * pSystemTable
  )
{
  EFI_STATUS    Status;

  DBG_ENTER ( );

  //
  //  Add the driver to the list of drivers
  //
  Status = EfiLibInstallDriverBindingComponentName2 (
             ImageHandle,
             pSystemTable,
             &gDriverBinding,
             ImageHandle,
             &gComponentName,
             &gComponentName2
             );
  ASSERT_EFI_ERROR (Status);
  if ( !EFI_ERROR ( Status )) {
    DEBUG (( DEBUG_POOL | DEBUG_INIT | DEBUG_INFO,
              "Installed: gEfiDriverBindingProtocolGuid on   0x%08x\r\n",
              ImageHandle ));
    DEBUG (( DEBUG_POOL | DEBUG_INIT | DEBUG_INFO,
              "Installed: gEfiComponentNameProtocolGuid on   0x%08x\r\n",
              ImageHandle ));
    DEBUG (( DEBUG_POOL | DEBUG_INIT | DEBUG_INFO,
              "Installed: gEfiComponentName2ProtocolGuid on   0x%08x\r\n",
              ImageHandle ));
  }
  DBG_EXIT_STATUS ( Status );
  return Status;
}
